/*!
    \copyright  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
                All rights reserved.
                Confidential and Proprietary - Qualcomm Technologies, Inc.
    \version    
    \file       dfu_transport_relay.c
    \ingroup    dfu
    \brief      Control and Manage Data Transfer to Peer through Tranport Layer.
*/

#ifdef INCLUDE_TRANSPORT_LAYER_RELAY

#include <logging.h>
#include <ps.h>
#include <upgrade.h>
#include <upgrade_peer.h>
#include "dfu_transport_relay_priv.h"
#include "dfu_peer.h"

/* dfu_transport_relay module task data */
dfu_transport_relay_task_data_t * dfu_transport_relay = NULL;

/* dfu_transport_relay module context data */
dfu_transport_relay_context_t * dfu_transport_relay_context = NULL;

TaskData dfu_transport_relay_task = { dfuTransportRelayMessageHandler };

/* Macro to get task data dfu_transport_relay */
#define dfuTransportRelay_GetTaskData()    (dfu_transport_relay)

/*! Macro to get the private context of dfu_transport_relay */
#define dfuTransportRelay_GetContext() (dfu_transport_relay_context)

/*! Get the Self stream data in context of dfu_transport_relay */
#define dfuTransportRelay_GetSelfStream() (&(dfuTransportRelay_GetContext()->dfu_relay_stream_self))

/*! Get the Peer stream data in context of dfu_transport_relay */
#define dfuTransportRelay_GetPeerStream() (&(dfuTransportRelay_GetContext()->dfu_relay_stream_peer))

/*! Get the Incoming stream data in context of dfu_transport_relay */
#define dfuTransportRelay_GetIncomingStream() (&(dfuTransportRelay_GetContext()->dfu_relay_stream_incoming))

/*! \brief Return TRUE if the queue is not empty, FALSE otherwise.
*/
static bool dfuTransportRelay_IsDataQueued(void)
{
    return dfuTransportRelay_GetContext()->queue_size;
}

/******************************************************************************
 * General Definitions
 ******************************************************************************/

/*! \brief Get state of dfu_transport_relay
*/
dfu_transport_relay_state_t dfuTransportRelayGetState(void)
{
    return dfuTransportRelay_GetTaskData()->state;
}

/*! \brief Set state of dfu_transport_relay
*/
void dfuTransportRelaySetState(dfu_transport_relay_state_t state)
{
    dfuTransportRelay_GetTaskData()->state = state;
}

/*! \brief Set private ctx of dfu_transport_relay
*/
void dfuTransportRelaySetCtx(dfu_transport_relay_context_t * ctx)
{
    DEBUG_LOG_VERBOSE("dfuTransportRelaySetCtx: size of DfuTransportRelaySetCtx is %d", sizeof(*ctx));
    if(dfuTransportRelay_GetContext() == NULL)
    {
        dfu_transport_relay_context = ctx;
    }
}

/*! \brief Return the element from the head of the queue
*/
UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T* dfuTransportRelayGetQueueHead(void)
{
    dfu_transport_relay_context_t* ctx = dfuTransportRelay_GetContext();
    UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T* msg;
    if(ctx->queue_size)
    {
        msg = ctx->data_queue[ctx->queue_head];
        return msg;
    }
    return NULL;
}

/*! \brief Add the element to the tail of the queue
*/
void dfuTransportRelayEnqueueMsg(UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T* message)
{
    dfu_transport_relay_context_t* ctx = dfuTransportRelay_GetContext();

    /* Check the capacity of the queue and increase it if needed. */
    if(!ctx->queue_max_size || ctx->queue_max_size == ctx->queue_size)
    {
        uint16 cnt_new = 0;
        uint16 cnt_old;
        UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T** new_queue;

        ctx->queue_max_size = ctx->queue_max_size? ctx->queue_max_size*2: 4;
        new_queue = PanicUnlessMalloc(sizeof(UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T*)*ctx->queue_max_size);

        DEBUG_LOG_INFO("dfuTransportRelayEnqueueMsg max size increased to %d", ctx->queue_max_size);

        /* Copy the elements from the old queue head to the end of the array in the new queue. */
        for(cnt_old = ctx->queue_head; cnt_old < ctx->queue_size; cnt_old++, cnt_new++)
        {
            new_queue[cnt_new] = ctx->data_queue[cnt_old];
        }

        /* Copy the elements from start of the old array to the tail of queue in the new queue. */
        for(cnt_old = 0; cnt_old < ctx->queue_tail; cnt_old++, cnt_new++)
        {
            new_queue[cnt_new] = ctx->data_queue[cnt_old];
        }

        ctx->queue_head = 0;
        ctx->queue_tail = ctx->queue_size;
        if(ctx->data_queue)
        {
            free(ctx->data_queue);
        }
        ctx->data_queue = new_queue;
    }

    /* Copy the new message to the tail of queue */
    ctx->data_queue[ctx->queue_tail] = message;
    ctx->queue_tail = (ctx->queue_tail +1) % ctx->queue_max_size;
    ctx->queue_size++;
    
    DEBUG_LOG_DEBUG("dfuTransportRelayEnqueueMsg queued message. length : %d, pending_to_process : %d", message->length, message->pending_to_process);
}

/*! \brief Remove the element from the head of the queue and free the memory assigned to it
*/
void dfuTransportRelayDequeueData(void)
{
    dfu_transport_relay_context_t* ctx = dfuTransportRelay_GetContext();
    if(dfuTransportRelay_IsDataQueued())
    {
        UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T* msg = ctx->data_queue[ctx->queue_head];
        ctx->queue_head = (ctx->queue_head + 1) % ctx->queue_max_size;
        ctx->queue_size--;
        DEBUG_LOG_DEBUG("dfuTransportRelayDequeueData message length: %d", msg->length);
        free(msg);
    }
}

/*! \brief Return the offset to request from host

    Compare the offsets requested by self and peer devices, and return lower offset
*/
uint32 dfuTransportRelayOffsetCalculator(void)
{
    return (dfuTransportRelay_GetSelfStream()->offset < dfuTransportRelay_GetPeerStream()->offset ? 
        dfuTransportRelay_GetSelfStream()->offset : dfuTransportRelay_GetPeerStream()->offset);
}

/*! \brief Return the data_bytes to request from host

    Compare the offsets requested by self and peer devices, and return size corresponding to lower offset.
*/
uint32 dfuTransportRelayReqSizeCalculator(void)
{
    return (dfuTransportRelay_GetSelfStream()->offset < dfuTransportRelay_GetPeerStream()->offset ? 
        dfuTransportRelay_GetSelfStream()->requested_size : dfuTransportRelay_GetPeerStream()->requested_size);
}

/*! \brief Update the pending_size and is_active fields of incoming stream
*/
void dfuTransportRelayUpdateIncomingStream(uint16 length)
{
    DEBUG_LOG_VERBOSE("dfuTransportRelayUpdateIncomingStream, pending_size: %d", dfuTransportRelay_GetIncomingStream()->pending_size);
    dfuTransportRelay_GetIncomingStream()->pending_size -= length;
    if(dfuTransportRelay_GetIncomingStream()->pending_size == 0)
    {
        dfuTransportRelay_GetIncomingStream()->is_active = FALSE;
    }
}

/*! \brief Update the pending_size and is_active fields of self stream
*/
void dfuTransportRelayUpdateSelfStream(uint16 length)
{
    DEBUG_LOG_VERBOSE("dfuTransportRelayUpdateSelfStream, pending_size: %d", dfuTransportRelay_GetSelfStream()->pending_size);
    dfuTransportRelay_GetSelfStream()->pending_size -= length;
    if(dfuTransportRelay_GetSelfStream()->pending_size == 0)
    {
        dfuTransportRelay_GetSelfStream()->is_active = FALSE;
    }
}

/*! \brief Update the pending_size and is_active fields of peer stream
*/
void dfuTransportRelayUpdatePeerStream(uint16 length)
{
    DEBUG_LOG_VERBOSE("dfuTransportRelayUpdatePeerStream, pending_size: %d", dfuTransportRelay_GetPeerStream()->pending_size);
    dfuTransportRelay_GetPeerStream()->pending_size -= length;
    if(dfuTransportRelay_GetPeerStream()->pending_size == 0)
    {
        dfuTransportRelay_GetPeerStream()->is_active = FALSE;
    }
}

/*! \brief Process the data received from host and stream data bytes to self device
*/
void dfuTransportRelayDataSendDataToSelf(UPGRADE_HOST_DATA_WITH_ACK_T* msg)
{
    UPGRADE_HOST_DATA_T msg_to_primary;

    msg_to_primary.length = msg->length;
    msg_to_primary.lastPacket = msg->lastPacket;
    msg_to_primary.data = msg->data;

    DEBUG_LOG_DEBUG("dfuTransportRelayDataSendDataToSelf, UPGRADE_HOST_DATA : writing to device");

    dfuTransportRelayUpdateSelfStream(msg->length);

    UpgradeHandleData(&msg_to_primary);
}

/*! \brief Process the data received from host and stream data bytes to peer device
*/
void dfuTransportRelayDataSendDataToPeer(UPGRADE_HOST_DATA_WITH_ACK_T* msg)
{
    MESSAGE_MAKE(msg_to_secondary, UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T);

    msg_to_secondary->length = msg->length + 4;
    msg_to_secondary->lastPacket = msg->lastPacket;
    msg_to_secondary->data = msg->data - 4;
    msg_to_secondary->dataCfm = msg->dataCfm;
    msg_to_secondary->transportTask = msg->transportTask;
    msg_to_secondary->pending_to_process = msg_to_secondary->length;

    /* TODO KPI Improvements : Move function to dfuTransportRelayDataWriteToPeer */
    dfuTransportRelayUpdatePeerStream(msg->length);

    dfuTransportRelayEnqueueMsg(msg_to_secondary);

    DEBUG_LOG_DEBUG("dfuTransportRelayDataSendDataToPeer, UPGRADE_HOST_DATA : writing to peer");

    dfuTransportRelayDataWriteToPeer();
}

/*! \brief Send Data Bytes request to host

    Collate the final offset, size and send the data request to host.
*/
void dfuTransportRelaySendDataReq(void)
{
    uint32 offset = dfuTransportRelayOffsetCalculator();
    uint32 req_size = dfuTransportRelayReqSizeCalculator();

    /* */
    dfuTransportRelay_GetTaskData()->primary_requested_data = dfuTransportRelay_GetSelfStream()->offset <= 
                                                              dfuTransportRelay_GetPeerStream()->offset ? TRUE : FALSE;
    dfuTransportRelay_GetTaskData()->secondary_requested_data = dfuTransportRelay_GetPeerStream()->offset <= 
                                                                dfuTransportRelay_GetSelfStream()->offset ? TRUE : FALSE;

    dfuTransportRelay_GetIncomingStream()->is_active = TRUE;
    dfuTransportRelay_GetIncomingStream()->offset = offset;
    dfuTransportRelay_GetIncomingStream()->requested_size = req_size;
    dfuTransportRelay_GetIncomingStream()->pending_size = dfuTransportRelay_GetIncomingStream()->requested_size;

    DEBUG_LOG_INFO("DfuTransportRelaySendDataReq: Requesting from host offset:%d, data_bytes:%d", offset, req_size);
    UpgradeGetFPtr()->SendBytesReq(req_size, 0);
}

/*! \brief Write data to peer L2CAP Sink

    Request the available slack in L2CAP connection with peer, retrieve data 
    from queue, and write to the sink.
*/
void dfuTransportRelayDataWriteToPeer(void)
{
    uint16 available_slack = DfuPeer_GetSlack();
    UPGRADE_TRANSPORT_RELAY_PEER_DATA_WITH_ACK_T* msg = dfuTransportRelayGetQueueHead();

    if(msg == NULL)
    {
        DEBUG_LOG_DEBUG("dfuTransportRelayDataWriteToPeer, queue empty");
        return;
    }

    uint16 offset = (msg->length - msg->pending_to_process);
    uint8 *start_ptr = (msg->data + offset);

    DEBUG_LOG("dfuTransportRelayDataWriteToPeer, available slack : %d", available_slack);

    if(msg->pending_to_process >= 0 && offset >= 0)
    {
        if(msg->pending_to_process > available_slack)
        {
            DfuPeer_L2capSendDataToPeer(start_ptr, available_slack);
            msg->pending_to_process -= available_slack;
        }
        else
        {
            DfuPeer_L2capSendDataToPeer(start_ptr, msg->pending_to_process);

            DEBUG_LOG_DEBUG("dfuTransportRelayDataWriteToPeer: pending_to_process=0, sending confirmation");

            UpgradeTransportSendDataCfm(msg->dataCfm, msg->transportTask);

            dfuTransportRelayDequeueData();
        }
    }
}

/*! \brief Handle messages recevied when dfu_transport_relay is in Idle state.
*/
bool dfuTransportRelayHandleIdle(MessageId id, Message message)
{
    bool handled = FALSE;

    DEBUG_LOG("dfuTransportRelayHandleIdle. id=MESSAGE:UpgradeMsgHost:%d", id);

    return handled;
}

/*! \brief Handle messages recevied when dfu_transport_relay is in Data Transfer state.
*/
bool dfuTransportRelayHandleDataTransfer(MessageId id, Message message)
{
    bool handled = FALSE;

    DEBUG_LOG("dfuTransportRelayHandleDataTransfer. id=MESSAGE:UpgradeMsgHost:%d", id);

    switch (id)
    {
        case UPGRADE_HOST_DATA:
        {
            if(message)
            {
                handled = TRUE;
                UPGRADE_HOST_DATA_WITH_ACK_T *msg = (UPGRADE_HOST_DATA_WITH_ACK_T *)message;
                DEBUG_LOG_INFO("dfuTransportRelayHandleDataTransfer, UPGRADE_HOST_DATA : total_received %d", msg->length);

                dfuTransportRelayUpdateIncomingStream(msg->length);

                if(dfuTransportRelay_GetTaskData()->primary_requested_data)
                {
                    dfuTransportRelayDataSendDataToSelf(msg);
                }

                if(dfuTransportRelay_GetTaskData()->secondary_requested_data)
                {
                    dfuTransportRelayDataSendDataToPeer(msg);
                }
            }
            if(!dfuTransportRelay_GetIncomingStream()->is_active)
            {
                dfuTransportRelaySetState(DFU_TRANSPORT_RELAY_STATE_IDLE);
            }
        }
        break;

        case DFU_PEER_TRANSPORT_NOTIFY_MMS_RCVD:
        {
            handled = TRUE;
            dfuTransportRelayDataWriteToPeer();
        }
        break;
        default:
            DEBUG_LOG_INFO("DfuTransportRelayMessageHandler, invalid message id: %d", id);
    }
    return handled;
}

/*! \brief Handle messages not handled in any of the state handlers
*/
void dfuTransportRelayDefaultHandler(MessageId id, Message message)
{
    DEBUG_LOG("dfuTransportRelayDefaultHandler. id=MESSAGE:UpgradeMsgHost:%d", id);

    switch (id)
    {
        case UPGRADE_HOST_IS_CSR_VALID_DONE_REQ:
        {
            UpgradeHandleCSRValidDoneReq();
        }
        break;

        default:
            DEBUG_LOG_INFO("dfuTransportRelayDefaultHandler, invalid message id: %d", id);
    }
}

/*! \brief Handle messages sent to dfu_transport_relay module.
*/
void dfuTransportRelayMessageHandler(Task task, MessageId id, Message message)
{
    UNUSED(task);
    bool handled = FALSE;

    DEBUG_LOG("DfuTransportRelayMessageHandler. id=MESSAGE:UpgradeMsgHost:%d", id);

    switch(dfuTransportRelayGetState())
    {
        case DFU_TRANSPORT_RELAY_STATE_IDLE:
        {
            handled = dfuTransportRelayHandleIdle(id,message);
        }
        break;

        case DFU_TRANSPORT_RELAY_STATE_DATA_TRANSFER:
        {
            handled = dfuTransportRelayHandleDataTransfer(id,message);
        }
        break;

        default:
            DEBUG_LOG_INFO("DfuTransportRelayMessageHandler, unexpected state: %d", dfuTransportRelayGetState());
    }

    if(!handled)
    {
        DEBUG_LOG("DfuTransportRelayMessageHandler. not handled id=MESSAGE:UpgradeMsgHost:%d", id);
        
    }
}

/*! \brief Initialise Device DFU Transport Relay task

    Called if INCLUDE_TRANSPORT_LAYER_RELAY is included.
*/
void DfuTransportRelayInit(void)
{
    dfu_transport_relay_task_data_t *dfu_tr_info;

    DEBUG_LOG_INFO("DfuTransportRelayInit");

    dfu_tr_info = (dfu_transport_relay_task_data_t *)PanicUnlessMalloc(sizeof(*dfu_tr_info));
    memset(dfu_tr_info, 0, sizeof(*dfu_tr_info));

    /* Initialize task data of dfu_transport_relay module */
    dfuTransportRelay_GetTaskData() = dfu_tr_info;

    dfu_transport_relay_context_t * dfu_transport_relay_context;
    
    dfu_transport_relay_context = PanicUnlessMalloc(sizeof(*dfu_transport_relay_context));
    memset(dfu_transport_relay_context, 0, sizeof(*dfu_transport_relay_context));

    /* Initialize context data of dfu_transport_relay module */
    dfuTransportRelaySetCtx(dfu_transport_relay_context);
}

/*! \brief Load Data Bytes Request info from Upgrade library into task_data

    Store Offset and Size in the data bytes request for the upgrade library
*/
void DfuTransportRelayLoadUpgradeDataReq(void)
{
    dfuTransportRelay_GetSelfStream()->is_active = TRUE;
    dfuTransportRelay_GetSelfStream()->offset = UpgradeGetDfuFileOffset();
    dfuTransportRelay_GetSelfStream()->requested_size = UpgradePartitionDataGetNextReqSize();
    dfuTransportRelay_GetSelfStream()->pending_size = dfuTransportRelay_GetSelfStream()->requested_size;

    DEBUG_LOG_DEBUG("DfuTransportRelayLoadUpgradeDataReq: Upgrade requested offset:%d, data_bytes:%d", dfuTransportRelay_GetSelfStream()->offset, dfuTransportRelay_GetSelfStream()->requested_size);
}

/*! \brief Load Data Bytes Request info from Upgrade Peer library into task_data

    Store Offset and Size in the data bytes request for the upgrade peer library
*/
void DfuTransportRelayLoadPeerDataReq(void)
{
    dfuTransportRelay_GetPeerStream()->is_active = TRUE;
    dfuTransportRelay_GetPeerStream()->offset = UpgradePeerGetDataBytesReq()->start_offset;
    dfuTransportRelay_GetPeerStream()->requested_size = UpgradePeerGetDataBytesReq()->data_bytes;
    dfuTransportRelay_GetPeerStream()->pending_size = dfuTransportRelay_GetPeerStream()->requested_size;

    DEBUG_LOG_DEBUG("DfuTransportRelayLoadPeerDataReq: Peer requested offset:%d, data_bytes:%d", dfuTransportRelay_GetPeerStream()->offset, dfuTransportRelay_GetPeerStream()->requested_size);
}

/*! \brief end Data Bytes request to host

    Collate the final offset and size and send the data request to host.
*/
bool DfuTrasportRelayIsSelfStreamActive(void)
{
    return dfuTransportRelay_GetSelfStream()->is_active;
}

/*! \brief end Data Bytes request to host

    Collate the final offset and size and send the data request to host.
*/
bool DfuTrasportRelayIsPeerStreamActive(void)
{
    return dfuTransportRelay_GetPeerStream()->is_active;
}

/*! \brief Start Data transfer through transport relay
*/
void DfuTransportRelayStartDataTransfer(void)
{
    /* Set Dfu Transport relay as clientTask of upgrade_host_if library to receive UPGRADE_HOST messages */
    UpgradeClientConnect(dfuTransportRelay_GetMsgHandler());
    /* Register Dfu Transport relay as sender for dfu_peer library to receive MMS messages*/
    DfuPeer_RegisterSender(dfuTransportRelay_GetMsgHandler());

    dfuTransportRelaySetState(DFU_TRANSPORT_RELAY_STATE_DATA_TRANSFER);

    /* Calculate the necessary minimum offset and corresponding size request and forward the request to host */
    dfuTransportRelaySendDataReq();
}

/*! \brief Deinitialise Device DFU Transport Relay task
*/
void DfuTransportRelayDestroy(void)
{
    DEBUG_LOG_INFO("DfuTransportRelayDestroy");

    dfuTransportRelaySetState(DFU_TRANSPORT_RELAY_STATE_IDLE);

    UpgradeClientReconnectLib();

    DfuPeer_DeregisterSender();

    dfu_transport_relay_context_t *ctx = dfuTransportRelay_GetContext();
    if(ctx)
    {
        free(ctx);
        dfu_transport_relay_context = NULL;
    }
}

#endif
